package com.espirit.eap.manager;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.annotation.PostConstruct;
import javax.annotation.Resource;

import org.apache.commons.lang.StringUtils;
import org.springframework.stereotype.Service;

import com.espirit.eap.pagelayout.ModelService;
import com.espirit.eap.pagelayout.SearchModelQuery;
import com.espirit.eap.sa.LoginUser;
import com.espirit.eap.sa.LoginUserService;
import com.googlecode.cswish.annotation.Protected;
import com.googlecode.cswish.annotation.Rest;
import com.googlecode.cswish.model.PageInfo;
import com.googlecode.cswish.model.Reference;
import com.googlecode.cswish.oosearch.QLInfo;
import com.googlecode.cswish.struts.spring.CacheService;
import com.googlecode.cswish.struts.spring.Executor;
import com.googlecode.cswish.struts.spring.GenericService;
import com.googlecode.cswish.struts.spring.SearchCallback;
import com.googlecode.cswish.util.Functions;
import com.googlecode.cswish.util.OgnlExUtil;

@Service
public class DictionaryService {

	@Resource
	private GenericService genericService;
	
	@Resource
	private CacheService cacheService;
	
	@Resource
	private ModelService modelService;
	
	@Resource
	private Functions functions;
	
	@Resource
	private OgnlExUtil ognlExUtil;
	
	@Resource
	private LoginUserService loginUserService;
	
	// key = type + value, see buildKey
	// value = name
	private Map<String, List<Dictionary>> cache;
	private Map<String, Boolean> invalidTypeCache;
	
	private Map<String, Executor<Boolean, String>> valueChangeEvent;
	
	@PostConstruct
	private void init() {
		functions.registCommonService("dictionary", this);
		
		cache = cacheService.newCaches(Dictionary.class);
		invalidTypeCache = cacheService.newCache(Boolean.class, 10000);
		Map tmp = cacheService.newCache(Executor.class);
		valueChangeEvent = tmp;
		
		// subscribe the change event
		Executor<Object, Object[]> execute = new Executor<Object, Object[]>() {
			@Override
			public Object execute(Object[] parameters) {
				Dictionary newModel = (Dictionary) parameters[0];
				Dictionary oldModel = (Dictionary) parameters[1];
				
				if (newModel != null) {				// add or update
					updateCacheValueByTypeName(newModel, false);
					
					String key = newModel.getType() + '\n' + newModel.getName();
					Executor<Boolean, String> event = valueChangeEvent.get(key);
					if (event != null) {
						event.execute(newModel.getValue());
					}
				} else {
					if (oldModel != null) {			// delete
						updateCacheValueByTypeName(oldModel, true);
						
						String key = oldModel.getType() + '\n' + oldModel.getName();
						Executor<Boolean, String> event = valueChangeEvent.get(key);
						if (event != null) {
							event.execute(null);
						}
					}
				}
				return null;
			}
		};
		genericService.on(Dictionary.class, GenericService.OPERATION_DELETE 
				+ ',' + GenericService.OPERATION_MERGE, execute, true);
		
		// resource name with display name
		modelService.registerSearchType("RES_DICT_CATEGORY", new Executor<PageInfo, SearchModelQuery>() {
			@Override
			public PageInfo<Map<String, String>> execute(SearchModelQuery searchModelQuery) {
				String category = searchModelQuery.query;
				PageInfo pageInfo;
				if (StringUtils.isEmpty(category)) {
					String ql = "select distinct a.type from " + Dictionary.class.getName() + " a";
					pageInfo = genericService.searchByQl(new QLInfo(ql, false), false);
				} else {
					String ql = "select distinct a.type from " + Dictionary.class.getName() + " a where a.type like ?";
					pageInfo = genericService.searchByQl(new QLInfo(ql, false, category), false);
				}
				List<Map<String, String>> categoryList = new ArrayList<>(pageInfo.getData().size());
				for (Object data : pageInfo.getData()) {
					String item = (String)data;
					Map<String, String> actionItem = new HashMap<>(3);
					actionItem.put("id", item);
					actionItem.put("name", item);
					categoryList.add(actionItem);
				}
				pageInfo.setData(categoryList);
				return pageInfo;
			}
		});
	}
	
	public PageInfo<Dictionary> search(Dictionary model, int start, int limit,
    		String sort, String dir, boolean newSearch, Reference<QLInfo> qlInfoRef) {
		SearchCallback searchCallback = null;
		
		LoginUser loginUser = loginUserService.getLoginUser();
		if (loginUser == null || !loginUser.isSuperAdmin()) {
			// Don't show the system dictionary if user is not super administrator
			searchCallback = new SearchCallback() {
				
				@Override
				public QLInfo afterBuildQL(boolean newSearch, QLInfo qlInfo) {
					if (newSearch) {
						if (qlInfo.getParams() == null || qlInfo.getParams().isEmpty()) {
							qlInfo.setQl(qlInfo.getQl() + " where (a.type != ?)");
							List<Object> qlParams = new ArrayList<>();
							qlParams.add(Dictionary.TYPE_SYSTEM);
							qlInfo.setParams(qlParams);
						} else {
							qlInfo.setQl(qlInfo.getQl() + " and (a.type != ?)");
							qlInfo.getParams().add(Dictionary.TYPE_SYSTEM);
						}
					}
					return qlInfo;
				}
			};
		}
		if (StringUtils.isEmpty(sort)) {
			sort = "type";
			dir = "ASC";
		}
    	return genericService.search(model, start, limit, sort, dir, newSearch, qlInfoRef, searchCallback, true, false);
    }
	
	
	/**
	 * Subscribe the value change event
	 * 
	 * @param name
	 * @param event
	 */
	public void on(String type, String name, Executor<Boolean, String> event) {
		valueChangeEvent.put(type + '\n' + name, event);
	}
	
	private void updateCacheValueByTypeName(Dictionary model, boolean isDeletion) {
		if (!isDeletion) {
			invalidTypeCache.remove(model.getType());
		}
		
		List<Dictionary> dictionaries = getDictionariesByType(model.getType());
		if (dictionaries != null) {
			boolean found = false;
			String name = model.getName();
			for (int i = 0, len = dictionaries.size(); i < len; i++) {
				Dictionary dictionary = dictionaries.get(i);
				if (name.equals(dictionary.getName())) {
					if (isDeletion) {		// remove the existed
						dictionaries.remove(i);
					} else {				// update the existed
						dictionary.setValue(model.getValue());
					}
					found = true;
					break;
				}
			}
			
			if (!found && !isDeletion) {		// it's new model
				dictionaries.add(model);
			}
		}
	}
	
	@Rest(type="search")
	public List<Dictionary> getDictionariesByType(String type){
		List<Dictionary> dictionaries = cache.get(type);
		if (dictionaries == null && invalidTypeCache.get(type) == null) {
			// try to load it from db
			Dictionary template = new Dictionary();
			template.setType(type);
			dictionaries = genericService.searchAll(template).getData();
			
			if (dictionaries.isEmpty()) {
				invalidTypeCache.put(type, Boolean.TRUE);
				dictionaries = null;
			} else {
				cache.put(type, dictionaries);
			}
		}
		return dictionaries;
	}

	@Protected
	public String getNameByTypeValue(String type, Object value) {
		if (type == null || value == null 
				|| value instanceof String && ((String)value).length() == 0) {
			return null;
		}
		
		List<Dictionary> dictionaries = getDictionariesByType(type);
		String name = null;
		if (dictionaries != null) {
			String strValue = value.toString();
			for (Dictionary dictionary : dictionaries) {
				if (strValue.equals(dictionary.getValue())) {
					name = dictionary.getName();
					break;
				}
			}
		}
		return name;
	}
	
	@Protected
	public <T> T getValue(String name, boolean enableCache, T defaultValue) {
		String value = getValueByTypeName(Dictionary.TYPE_SYSTEM, name);
		
		T ret;
		if (value == null) {
			ret = defaultValue;
		} else {
			if (defaultValue == null || defaultValue instanceof String) {
				ret = (T)value;
			} else {
				Class<T> clazz = (Class<T>)defaultValue.getClass();
				ret = ognlExUtil.convertValue(value, clazz);
			}
		}
		return ret;
	}
	
	@Protected
	public String getValueByTypeName(String type, String name) {
		if (type == null || name == null  || name.length() == 0) {
			return null;
		}
		
		List<Dictionary> dictionaries = getDictionariesByType(type);
		
		String value = null;
		for (Dictionary dictionary : dictionaries) {
			if (name.equals(dictionary.getName())) {
				value = dictionary.getValue();
				break;
			}
		}
		return value;
	}
	
	@Protected
	public Dictionary mergeByTypeValue(Dictionary dictionary) {
		Dictionary example = new Dictionary();
		example.setType(dictionary.getType());
		example.setValue(dictionary.getValue());
		Dictionary existed = genericService.searchAll(example).getData(0);
		if (existed != null) {
			boolean changed = false;
			if (!StringUtils.equals(existed.getName(), dictionary.getName())) {
				changed = true;
				existed.setName(dictionary.getName());
			}
			if (!StringUtils.equals(existed.getDescription(), dictionary.getDescription())) {
				changed = true;
				existed.setDescription(dictionary.getDescription());
			}
			if (changed) {
				genericService.merge(existed);
			}
		} else {
			existed = genericService.merge(dictionary);
		}
		return existed;
	}
	
	@Protected
	public Dictionary mergeByTypeName(Dictionary dictionary, boolean enableUpdate) {
		Dictionary example = new Dictionary();
		example.setType(dictionary.getType());
		example.setName(dictionary.getName());
		Dictionary existed = genericService.searchAll(example).getData(0);
		if (existed != null) {
			if (enableUpdate) {
				boolean changed = false;
				if (!StringUtils.equals(existed.getValue(), dictionary.getValue())) {
					changed = true;
					existed.setValue(dictionary.getValue());
				}
				if (!StringUtils.equals(existed.getDescription(), dictionary.getDescription())) {
					changed = true;
					existed.setDescription(dictionary.getDescription());
				}
				if (changed) {
					genericService.merge(existed);
				}
			}
		} else {
			existed = genericService.merge(dictionary);
		}
		return existed;
	}
}